package com.jvm.classfile;

import com.jvm.classfile.model.*;
import com.sun.org.apache.bcel.internal.Const;

import java.io.DataInputStream;

/**
 * @program: diy_simple_jvm
 * @description: Class文件读取器
 * @author: CaoYong
 * @create: 2021-07-08 14:40
 **/
public class ClassReader {

    public static ClassFile read(DataInputStream is) throws Exception {

        int magic = is.readInt();
        int minorVersion = is.readUnsignedShort();
        int majorVersion = is.readUnsignedShort();

        int cpSize = is.readUnsignedShort();
        CpInfo[] cpInfos = new CpInfo[cpSize];
        for (int i = 1; i < cpSize; i++) {
            int tag = is.readUnsignedByte();
            byte[] info = null;
            int step = 0;
            switch(tag) {
                case Const.CONSTANT_Fieldref:
                case Const.CONSTANT_Methodref:
                case Const.CONSTANT_NameAndType:
                case Const.CONSTANT_Integer:
                case Const.CONSTANT_Float:
                    info = new byte[4];
                    is.read(info);
                    break;
                case Const.CONSTANT_Class:
                case Const.CONSTANT_String:
                    info = new byte[2];
                    is.read(info);
                    break;
                case Const.CONSTANT_Utf8:
                    int ulen = is.readUnsignedShort();
                    info = new byte[ulen];
                    is.read(info);
                    break;
                case Const.CONSTANT_Double:
                case Const.CONSTANT_Long:
                    info = new byte[8];
                    is.read(info);
                    step = 1;
                    break;
                default:
                    throw new IllegalStateException("unknown tag:" + tag);
            }
            cpInfos[i] = new CpInfo(new U1(tag), info);
            i += step;
        }

        int accessFlags = is.readUnsignedShort();
        int thisClass = is.readUnsignedShort();
        int superClass = is.readUnsignedShort();

        int interfacesCount = is.readUnsignedShort();
        U2[] interfaces = new U2[interfacesCount];
        for (int i = 0; i < interfacesCount; i++) {
            interfaces[i] = new U2(is.readUnsignedShort());
        }

        int fieldsCount = is.readUnsignedShort();
        FieldInfo[] fields = new FieldInfo[fieldsCount];
        for (int i = 0; i < fieldsCount; i++) {
            int fieldAccessFlags = is.readUnsignedShort();
            int fieldNameIndex = is.readUnsignedShort();
            int fieldDescriptor = is.readUnsignedShort();
            int fieldAttributesCount = is.readUnsignedShort();
            AttributeInfo[] fieldAttributeInfo = new AttributeInfo[fieldAttributesCount];
            for (int i1 = 0; i1 < fieldAttributesCount; i1++) {
                int fieldAttributeNameIndex = is.readUnsignedShort();
                int fieldAttributeLength = is.readInt();
                byte[] info = new byte[fieldAttributeLength];
                is.read(info);

                fieldAttributeInfo[i1] = new AttributeInfo(new U2(fieldAttributeNameIndex), new U4(fieldAttributeLength),
                        info);
            }

            fields[i] = new FieldInfo(
                    new U2(fieldAccessFlags),
                    new U2(fieldNameIndex),
                    new U2(fieldDescriptor),
                    new U2(fieldAttributesCount),
                    fieldAttributeInfo);
        }

        int methodsCount = is.readUnsignedShort();
        MethodInfo[] methods = new MethodInfo[methodsCount];
        for (int i = 0; i < methodsCount; i++) {
            int methodAccessFlags = is.readUnsignedShort();
            int methodNameIndex = is.readUnsignedShort();
            int methodDescriptor = is.readUnsignedShort();
            int methodAttributesCount = is.readUnsignedShort();
            AttributeInfo[] methodAttributeInfo = new AttributeInfo[methodAttributesCount];
            for (int j = 0; j < methodAttributesCount; j++) {
                int methodAttributeNameIndex = is.readUnsignedShort();
                int methodAttributeLength = is.readInt();
                byte[] info = new byte[methodAttributeLength];
                is.read(info);

                methodAttributeInfo[j] = new AttributeInfo(
                        new U2(methodAttributeNameIndex),
                        new U4(methodAttributeLength),
                        info);
            }

            methods[i] = new MethodInfo(
                    new U2(methodAccessFlags),
                    new U2(methodNameIndex),
                    new U2(methodDescriptor),
                    new U2(methodAttributesCount),
                    methodAttributeInfo);
        }

        int attributeCount = is.readUnsignedShort();
        AttributeInfo[] attributeInfos = new AttributeInfo[attributeCount];
        for (int i = 0; i < attributeCount; i++) {
            int attributeNameIndex = is.readUnsignedShort();
            int attributeLength = is.readInt();
            byte[] info = new byte[attributeLength];
            is.read(info);

            attributeInfos[i] = new AttributeInfo(new U2(attributeNameIndex), new U4(attributeLength), info);
        }



        return new ClassFile(
                new U4(magic),
                new U2(minorVersion),
                new U2(majorVersion),
                new U2(cpSize),
                cpInfos,
                new U2(accessFlags),
                new U2(thisClass),
                new U2(superClass),
                new U2(interfacesCount),
                interfaces,
                new U2(fieldsCount),
                fields,
                new U2(methodsCount),
                methods,
                new U2(attributeCount),
                attributeInfos
        );
    }


}
